//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
//
//	File		 :	ParseCLUSTAL.cpp
//	Contents	 :	Source file for class ParseCLUSTAL
//	Creation Date:	22/12/1999
//	Last Modified:	23/12/1999	lg
//	tabs width	 :	every 4 characters
//
//888888888888888888888888888888888888888888888888888888888888888888888888888888888888888


#include <sstream>
#include <string>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <vector>
#include "parseclustal.h"
#include "msfresource.h"
#include "std_helper.h"
#ifdef _MFC_VER	// mfc specific code to follow
#include "../common/mfc_stdcpp.h"
#else
#include "../common/nonmfc_stdcpp.h"
#endif


//_______________________________________________________________________________________
//
//	Constructor
//
//		Just initializes variables
//		Last modified: lg 23/12/1999
//
//_______________________________________________________________________________________
CParseCLUSTAL::CParseCLUSTAL(CParseMessageCallback& callback)
	: CParse(callback, IDS_ERR_INVALID_CLUSTALFILE, IDS_ERR_CLUSTALFILE)
	//("This is not a valid CLUSTAL file.\r\n");
	//"There is an error in the CLUSTAL file.\r\n"
{
}

bool CParseCLUSTAL::SkipPHDPreamble()
{
	char* nextLine = Data_Stream;
	Data_Stream = 0;
	while (nextLine)
	{
		// get next line
		PrepareNextLine(nextLine, Data_Stream, LineNumber);
		if (!nextLine)
			return false;

		// skip spaces
		SkipSpaces(Data_Stream);

		// ignore blank lines
		if (!*Data_Stream)
			continue;

		if (stricmp(std::string(Data_Stream, 5), "# saf") == 0)
		{
			Data_Stream = nextLine +1;
			return true;
		}
	}
	return false;
}

//_______________________________________________________________________________________
//
//	Do Parse
//
//		Parse CLUSTAL_Stream into Sequences of amino acids
//		Input		: msf_Stream
//		Output		: outputSequence
//		Last modified: lg 23/12/1999
//
//_______________________________________________________________________________________
namespace{
inline bool SeparateNameData(char* nameStart, char*& nameEnd, char*& dataStart, char*& dataEnd)
{
	assert(nameStart);
	// skip to next whitespace
	nameEnd = nameStart + 1;
	while (*nameEnd && !isspace(*nameEnd))
		++nameEnd;
	// nameEnd points to one past name


	dataStart = nameEnd;

	// skip past initial whitespace
	CParse::SkipSpaces(dataStart);

	dataEnd = dataStart;
	CParse::SkipToSpace(dataEnd);

	return dataStart != dataEnd;
}
}

bool CParseCLUSTAL::DoParse(char* const data_Stream, CSequence& outputSequence)
{
	assert(data_Stream);
	assert(	outputSequence.FormatType == CSequence::eCLUSTAL ||
			outputSequence.FormatType == CSequence::ePHDSAF);

	Data_Stream = data_Stream;

	// clear all variables
	LineNumber = 0;
	outputSequence.Clear();

	bool HeaderParsed	= false;
	if (outputSequence.FormatType == CSequence::ePHDSAF)
	{
		SetErrMsges(IDS_ERR_INVALID_PHDFILE, IDS_ERR_PHDFILE);
		if (ParseErr(!SkipPHDPreamble(), IDS_ERR_NO_PHDSAF_DATA, LineNumber))
			return false;
		// no "CLUSTAL"
		HeaderParsed = true;
	}

	int	lastNameMatch	= 0;

	// get line number for diagnostic purposes
	// save Data_Stream variable for convenience
	char* nextLine = Data_Stream;
	Data_Stream = 0;
	while (nextLine)
	{
		PrepareNextLine(nextLine, Data_Stream, LineNumber);

		// the only lines beginning with space are empty or to indicate consensus: skip
		if (!*Data_Stream || isspace(*Data_Stream))
			continue;

		// the msf part of phd files are delimited by 80 underscores
		if (outputSequence.FormatType== CSequence::ePHDSAF &&
			CheckMatchChars(Data_Stream, '_', 70))
			break;


		// "The keyword CLUSTAL must appear at the beginning of a line before the alignment (on line %d)."
		if (ParseErr(!HeaderParsed &&
					(nextLine - Data_Stream >= 7) &&
					strncmp("CLUSTAL", Data_Stream, 7),
			IDS_ERR_CLUSTAL_MISSINGHEADER, LineNumber))
			return false;

		if (!HeaderParsed)
		{
			HeaderParsed = true;
			continue;
		}

		// saf lines starting with # are comments
		if (outputSequence.FormatType == CSequence::ePHDSAF && *Data_Stream == '#')
			continue;


		char* dataStart, *dataEnd, *nameEnd;
		// "Line %d contains a sequence name "%s" not followed by any data and will be ignored."
		bool ok = SeparateNameData(Data_Stream, nameEnd, dataStart, dataEnd);
		if (ParseWarn(!ok,
				IDS_ERR_CLUSTAL_MISSINGDATA, LineNumber,
				std::string(Data_Stream, nameEnd - Data_Stream).c_str()))
			return false;

		if (!ok)
			continue;
		
		// find existing sequence, otherwise create a new one
		std::string name(Data_Stream, nameEnd-Data_Stream);
		if (MatchNames(outputSequence.Names, name, lastNameMatch) == -1)
		{
			outputSequence.Names.push_back(name);
			outputSequence.Data.push_back(std::string());
			lastNameMatch = outputSequence.Data.size() - 1;
		}

		// just append for CLUSTAL
		if (outputSequence.FormatType != CSequence::ePHDSAF)
		{
			assert((unsigned)lastNameMatch < outputSequence.Count());
			outputSequence.Data[lastNameMatch].append(dataStart, dataEnd - dataStart);
		}

		// append all but whitespace for SAF
		else
		{
			unsigned lineLength = 0;
			// Add all non-whitespace
			while (*dataStart)
			{
				if(!isspace(*dataStart))
				{
					outputSequence.Data[lastNameMatch] += *dataStart;
					++lineLength;
				}
				++dataStart;
			}

			// "The sequence on Line %d contains too many amino acids."
			if (ParseWarn(lineLength > 60, IDS_WARN_MSF_TOOMANYAA, LineNumber))
				return false;
		}
		
	}

	// make sure there is data in some of the sequences
	// "No sequence data could be found in any of the %d lines."
	if(ParseErr(outputSequence.Count() ==0, IDS_ERR_CLUSTAL_NODATA, LineNumber))
		return false;


	// make sure all sequences are the same in length
	if (!outputSequence.CheckLengthsEq())
	{
		ParseErrLength(outputSequence);
		return false;
	}

	outputSequence.Len = outputSequence.Data[0].length();

	// get next line	
	if (nextLine)
		Data_Stream = nextLine + 1;
	else
		Data_Stream = 0;
	return true;
}

